/**
 * Flat FFT Hardware Simulation Test
 *
 * This test program validates the correctness of flat FFT hardware implementations.
 * Flat FFT refers to non-pipelined, fully parallel FFT implementations that are
 * suitable for applications with larger area budgets but higher throughput requirements.
 *
 * Features:
 * - Non-pipelined combinatorial FFT implementations
 * - Complete parallel processing for maximum throughput
 * - Configurable bit widths and scaling parameters
 * - Comprehensive test vector validation
 * - Support for both forward and inverse FFT transforms
 *
 * Test Flow:
 * 1. Generate test vector data using reference implementations
 * 2. Compile FFT hardware modules with specified parameters
 * 3. Process input data frame by frame and validate outputs
 * 4. Calculate test pass rates and failure statistics
 *
 * Command Line Arguments:
 * args(0): Inverse FFT flag (0/1, default 0)
 * args(1): FFT size (number of points)
 * args(2): Scaling factor
 * args(3): Input bit width (optional, default 18)
 * args(4): Output bit width (optional, default 18)
 *
 * @author FFTE Project
 * @since 2021
 *
 * @example
 * // Test 64-point forward FFT with scaling 3
 * runMain test.FlatStudy 0 64 3
 *
 * // Test 128-point inverse FFT with custom bit widths
 * runMain test.FlatStudy 1 128 4 20 24
 *
 * @see [[ffte.evaluate.cases.Sim]] for the simulation framework
 * @see [[ffte.core.flat.FFFT]] and [[ffte.core.flat.FIFFT]] for FFT implementations
 */

package ffte.test

import spinal.core._
import spinal.core.sim._

import breeze.math.Complex

import ffte.evaluate.cases.{Sim,FFTCase}
import ffte.evaluate.vectors.Cplx

import ffte.core.flat.{FIFFT,FFFT,Flat}
import ffte.core.streamed.{streamedComponents}

import ffte.property.{getShiftMethod,getFFTIW,getFFTOW,FFTIOConfig}

import ffte.algorithm.FFTGen
import scala.util.Random._

/**
 * Flat FFT test simulation object
 *
 * Provides comprehensive testing infrastructure for flat FFT hardware implementations.
 * Extends the Sim trait to leverage the existing testing framework with specialized
 * validation for parallel, non-pipelined FFT architectures.
 */
object FlatStudy extends Sim {

    /** Input data bit width configuration (default 18 bits) */
    var dW = getFFTIW()

    /** Output data bit width configuration (default 18 bits) */
    var oW = getFFTOW()

    /**
     * Generate random clock enable signal for realistic timing simulation.
     *
     * @return Random boolean value for clock enable control
     */
    def ce() = nextBoolean

    /**
     * Main FFT simulation function
     *
     * Executes the complete FFT hardware simulation with test vector generation,
     * hardware compilation, and result validation. Supports both forward and
     * inverse FFT transforms with configurable parameters.
     *
     * @param _inv Flag indicating whether to perform inverse FFT transformation
     * @return Tuple of (overall test success, failure count)
     */
    def sim(_inv:Boolean) : (Boolean,Int) = {
        inv = _inv

        // Calculate scaling factor: base scaling + bit width difference compensation
        S   = getShiftMethod(N) + (if(dW>oW) (dW-oW) else 0)
        var delay = 0
        val tv = test_vectors  // Get test vector data
        println(s"simulation $N gen test vectors ${getFFTIW()} ----$S")

        // Compile FFT hardware and start simulation
        SimConfig.compile {
            // Select FFT or IFFT module based on transform type
            val fft = if(inv) FIFFT(N) else FFFT(N)
            delay = fft.delay  // Record FFT module delay
            fft
        }.doSim{ dut =>
            // Simulation initialization: start clock signal
            done({
                dut.clockDomain.forkStimulus(period = 10)
            },{
                // Current frame and reference frame calculation (considering FFT delay)
                val tframe = idx%CC                           // Current input frame index
                val rframe = (idx-delay+10*CC)%CC               // Corresponding reference output frame index
                val d : Array[Cplx] = tv(tframe).d            // Current frame input data
                val q : Array[Cplx] = tv(rframe).q            // Expected output data

                // Write input data to FFT module
                for(i <- 0 until N) {
                    dut.io.d.d(i).re.d.raw #= d(i).re  // Real part
                    dut.io.d.d(i).im.d.raw #= d(i).im  // Imaginary part
                }

                // Set clock enable signal
                dut.io.ce #= ce()
                dut.clockDomain.waitRisingEdge()

                // Validate output during valid clock cycles
                if(dut.io.ce.toBoolean) {
                    if(idx>delay) {
                        // Verify FFT output results
                        for(i <- 0 until N) {
                            // Read FFT output data (convert to complex format)
                            val r = Cplx(dut.io.q.d(i).re.d.raw.toInt,dut.io.q.d(i).im.d.raw.toInt)

                            // Check if output is within tolerance (L1 norm <= 32)
                            if((r-q(i)).norm1>32) {
                                if(debug>2) {
                                    println(s"$rframe($i) $r != ${q(i)}, ${tv(rframe).d(i)}")
                                }
                                ook = false  // Mark test failure
                                ok  = false
                            }
                        }
                    }

                    // If current frame test failed, increment failure count
                    if(!ook) failcnt += 1
                    next  // Move to next frame test
                }
            })
        }

        // Return test results: (overall test success, failure count)
        (failcnt!=(C-delay),failcnt)
    }

    /**
     * Single FFT size test
     *
     * Executes a comprehensive test for a specific FFT configuration
     * with detailed debugging and validation output.
     *
     * @param n FFT size (number of points)
     * @param k Scaling factor index
     * @param inv Flag for inverse FFT transform
     */
    def singleTest(n:Int,k:Int,inv:Boolean):Unit = {
        debug = 3      // Maximum debug level for detailed output
        N = n          // Set FFT size
        K = k          // Set scaling factor
        FFTGen.Winograd  // Use Winograd algorithm optimization

        // Run simulation under specified I/O configuration
        val (_ook,failcnt) = FFTIOConfig(dW,oW) on sim(inv)
        if(_ook) println(s"$n($inv): PASS($failcnt)") else println(s"$n: FAIL")
    }

    /**
     * Batch test for multiple FFT configurations
     *
     * Executes comprehensive testing across multiple FFT sizes and
     * configurations, logging results to file for analysis.
     *
     * @param cases Map of test cases (FFT size -> scaling factor)
     * @param inv Flag for inverse FFT transform
     */
    def caseTest(cases:Map[Int,Int],inv:Boolean): Unit = {
        K = 0      // Reset scaling factor
        debug = 1  // Medium debug level
        val nList = cases.map{ case(n,_) => n }.toList.sorted
        nList.map { n =>
            N = n
            println(s"start $N test")
            val (_ook,failcnt) = sim(inv)
            val msg = if(_ook) s"$n($inv): PASS($failcnt)" else s"$n: FAIL"
            Sim.log2file("tmp/flat.log",msg)
            (n,_ook,failcnt)
        }.foreach{case (n,oook,failcnt) =>
            if(oook) println(s"$n($inv): PASS($failcnt)") else println(s"$n: FAIL")
        }
    }
  /**
     * Main entry point for flat FFT testing
     *
     * Parses command line arguments and executes appropriate test scenarios.
     * Supports both single FFT size testing and comprehensive batch testing.
     *
     * @param args Command line arguments:
     *             args(0): Inverse FFT flag (0/1, default 0)
     *             args(1): FFT size (required for single test)
     *             args(2): Scaling factor (required for single test)
     *             args(3): Input bit width (optional, default 18)
     *             args(4): Output bit width (optional, default 18)
     */
    def main(args: Array[String]) {
        C = 100
        ffte.default.core.GlobeSetting.evaluateNative = false
        val inv = if(args.length>0) (args(0).toInt==1) else false
        if(args.length>1) {
            val n = args(1).toInt
            val k = args(2).toInt
            if(args.length>3) {
                dW = args(3).toInt
            }
            if(args.length>4) {
                oW = args(4).toInt
            }
            println("In sim")
            singleTest(n,k,inv)
        } else caseTest(FFTCase.basic,inv)
    }
}

object Flat2StreamedStudy extends Sim {
    var dW = getFFTIW()
    var oW = getFFTOW()
    def valid() = nextBoolean
    def ready() = nextBoolean
    def sim(_inv:Boolean) : (Boolean,Int) = {
        inv = _inv
        S   = getShiftMethod(N) + (if(dW>oW) (dW-oW) else 0)
        var delay = 0
        val tv = test_vectors
        println(s"simulation $N gen test vectors ${getFFTIW()}")
        val fp = if(rgen) (FFTGen.rgen[Flat](N)) else (FFTGen.gen[Flat](N)) 
        val tab     = fp.load_tab
        val out_tab = fp.store_tab
            
        SimConfig.withWave.compile {
            val m = new Flat(S)
            val fft = new m.toStreamed(fp.fft,"top").asInstanceOf[streamedComponents]
            delay = fft.delay
            fft
        }.doSim{ dut =>
            done({
                dut.clockDomain.forkStimulus(period = 10)
                dut.io.q.ready        #= true
                dut.io.d.payload.last #= true
                dut.clockDomain.waitRisingEdge()
                dut.io.d.payload.last #= false
            },{
                val tframe = idx%CC
                val rframe = (idx-delay+10*CC)%CC
                val d : Array[Cplx] = tv(tframe).d
                val q : Array[Cplx] = tv(rframe).q
                for(i <- 0 until N) {
                    dut.io.d.d(i).re.d.raw #= d(tab(i)).re
                    dut.io.d.d(i).im.d.raw #= d(tab(i)).im
                }
                if(tframe==CC-1) {
                    dut.io.d.payload.last #= true
                } else {
                    dut.io.d.payload.last #= false
                }
                dut.io.d.valid            #= valid()
                dut.io.q.ready            #= ready()
                dut.clockDomain.waitRisingEdge()
                if(dut.io.q.valid.toBoolean && dut.io.q.ready.toBoolean) {
                    if(idx>delay) {
                        for(i <- 0 until N) {
                            val ioq = dut.io.q.payload.fragment
                            val r = Cplx(ioq.d(i).re.d.raw.toInt,ioq.d(i).im.d.raw.toInt)
                            val cq = q(out_tab(i))
                            if((r-cq).norm1>32) {
                                if(debug>2) {
                                    println(s"$rframe($i) $r != ${cq}, ${tv(rframe).d(i)}")
                                }
                                ook = false
                                ok  = false
                            }
                        }
                        if(dut.io.q.payload.last.toBoolean) {
                            if(rframe!=CC-1) {
                                println(s"last flag not on ${CC-1}")
                            }
                        }
                    }
                    if(!ook) failcnt += 1
                    next
                }
            })
        }
        (failcnt!=(C-delay),failcnt)
    }
    def singleTest(n:Int,k:Int,inv:Boolean):Unit = {
        debug = 3
        N = n
        K = k
        FFTGen.Winograd
        val (_ook,failcnt) = FFTIOConfig(dW,oW) on sim(inv)
        if(_ook) println(s"$n($inv): PASS($failcnt)") else println(s"$n: FAIL")
    }
    def caseTest(cases:Map[Int,Int],inv:Boolean): Unit = {
        K = 0
        debug = 1
        val nList = cases.map{ case(n,_) => n }.toList.sorted
        nList.map { n =>
            N = n
            println(s"start $N test")
            val (_ook,failcnt) = sim(inv)
            val msg = if(_ook) s"$n($inv): PASS($failcnt)" else s"$n: FAIL"
            Sim.log2file("tmp/flat.log",msg)
            (n,_ook,failcnt)
        }.foreach{case (n,oook,failcnt) =>
            if(oook) println(s"$n($inv): PASS($failcnt)") else println(s"$n: FAIL")
        }
    }
    def main(args: Array[String]) {
        C = 100
        ffte.default.core.GlobeSetting.evaluateNative = false
        val inv = if(args.length>0) (args(0).toInt==1) else false 
        if(args.length>1) {
            val n = args(1).toInt
            val k = args(2).toInt
            if(args.length>3) {
                dW = args(3).toInt
            }
            if(args.length>4) {
                oW = args(4).toInt
            }
            singleTest(n,k,inv)
        } else caseTest(FFTCase.basic,inv)
    }
}